package com.xunk.equipment.service.ruying;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.xunk.common.core.constant.CacheConstants;
import com.xunk.common.core.constant.HttpStatus;
import com.xunk.common.core.constant.MessageNotifyConstants;
import com.xunk.common.core.enums.DeviceEnum;
import com.xunk.common.core.enums.RuyingDeviceEnum;
import com.xunk.common.core.utils.*;
import com.xunk.common.redis.service.RedisService;
import com.xunk.common.security.utils.SecurityUtils;
import com.xunk.equipment.config.EquipmentConfig;
import com.xunk.equipment.domain.EquipmentDO;
import com.xunk.equipment.domain.EquipmentPropertiesData;
import com.xunk.equipment.domain.ruying.RuYingResponse;
import com.xunk.equipment.influxdb.InfluxCustomMapper;
import com.xunk.equipment.influxdb.entity.RuyingEquiqmentInfluxEntity;
import com.xunk.equipment.mapper.HomeMapper;
import com.xunk.equipment.service.BaseService;
import com.xunk.equipment.websocket.WebsocketManager;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @ProjectName: xunk
 * @Package: com.xunk.equipment.service.ruying.controller.sensor.service.impl
 * @ClassName: RuoYingControlServiceImpl
 * @Author: dongxiaoxin
 * @Description: 如影控制设备服务实现
 * @Date: 2021/3/26 9:43
 * @Version: 1.0
 */
@Slf4j
@Service
public class RuYingControlService {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    EquipmentConfig equipmentConfig;

    @Autowired
    InfluxCustomMapper customMapper;

    @Autowired
    @Lazy
    BaseService baseService;

    @Autowired
    RedisService redisService;


    private static final String OK_MSG = "ok";

    public Object getProperties(String ownId, String pid, boolean needInsertInflux) {

        String dvivceShadowUrl = equipmentConfig.getPropertiesUrl();

        Map<String, String> header = HttpUtil.getHeader4Ruying(equipmentConfig.getAppId(), JwtUtil.getRuyingGwt(equipmentConfig.getSerectKey(), ownId));
        String req = String.format("{\"properties\": [\"%s\"]}", pid);
        JSONObject jsonObject = HttpUtil.doPoststr(dvivceShadowUrl, req, header);

        logger.info(jsonObject.toJSONString());
        RuYingResponse<EquipmentPropertiesData> response = doCheckResponse(jsonObject);
        EquipmentPropertiesData data = response.getData();
        List<EquipmentPropertiesData.PropertyData> properties = data.getProperties();

        Object value = null;
        for (EquipmentPropertiesData.PropertyData property : properties) {

            if (StringUtils.isNotEmpty(property.getDescription())) {
                logger.info("获取属性返回结果：piid: " + property.getPid() + ",Status:" + property.getStatus() + ",Description:" + property.getDescription());
                throw new RuntimeException(property.getDescription());
            }
            value = property.getValue();
            Long deptId = baseService.findDeptIdByOwnId(ownId);

            cacheRuyingEquipmentProperty(deptId, pid, String.valueOf(value));
            //  存入时序数据库
            if (needInsertInflux) {
                String[] ids = getIds(property.getPid());
                RuyingEquiqmentInfluxEntity entity = new RuyingEquiqmentInfluxEntity(ids[0], Integer.parseInt(ids[1]), Integer.parseInt(ids[2]),
                        value.toString(), deptId);
                customMapper.save(entity);
            }
        }
        return value;
    }

    /**
     * 设置如影设备基础属性
     *
     * @param ownId
     * @param properties
     * @return
     */
    public String setProperties(String ownId, EquipmentPropertiesData properties) {

        String url = equipmentConfig.getSetPropertiesUrl();
        Map<String, String> header = HttpUtil.getHeader4Ruying(equipmentConfig.getAppId(), JwtUtil.getRuyingGwt(equipmentConfig.getSerectKey(), ownId));
        JSONObject jsonObject = HttpUtil.doPoststr(url, JSONObject.toJSONString(properties), header);

        RuYingResponse<EquipmentPropertiesData> response = doCheckResponse(jsonObject);
        List<EquipmentPropertiesData.PropertyData> list = response.getData().getProperties();
        for (EquipmentPropertiesData.PropertyData property : list) {
            if (StringUtils.isNotEmpty(property.getDescription())) {
                logger.info("设置属性返回结果：piid: " + property.getPid() + ",Status:" + property.getStatus() + ",Description:" + property.getDescription());
                // 如果控制失败，则找到该属性对应的原始的值通过websocket发送给APP
                Long userId = SecurityUtils.getUserId(false);
                if (DataUtils.isNotEmpty(userId)) {
                    sendControlFailInfo(property, userId, property.getDescription());
                }
                throw new RuntimeException(property.getDescription());
            }
        }
        return response.getMsg().equals("ok") ? "success" : "fail";
    }

    private void sendControlFailInfo(EquipmentPropertiesData.PropertyData property, Long userId, String description) {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String pid = property.getPid();
            int i = pid.indexOf(".");
            String did = pid.substring(0, i);
            String p = pid.substring(i);
            EquipmentDO equipmentInfo = baseService.getEquipmentInfo(did);
            RuyingDeviceEnum ruyingDeviceEnum = RuyingDeviceEnum.parseDeviceEnumByDeviceType(DeviceEnum.parseDeviceEnum(equipmentInfo.getDeviceType()));
            String s = ruyingDeviceEnum.getProperties().get(p);
            Map<String, String> shadowByDeviceSn = getShadowByDeviceSn(did, equipmentInfo.getDeptId());
            String value = shadowByDeviceSn.get(pid);
            String online = shadowByDeviceSn.get("online");
            Map<String, Object> propertyMap = new HashMap<>();
            propertyMap.put(s, value);
            propertyMap.put("online", online);
            Map<String, Object> data = new HashMap<>();
            data.put("deviceSn", did);
            data.put("description", description);
            data.put("properties", propertyMap);
            data.put("deviceType", equipmentInfo.getDeviceType());

            Map<String, Object> pr = new HashMap<>();
            pr.put("code", HttpStatus.SUCCESS);
            pr.put("msg", "设备消息");
            pr.put("type", MessageNotifyConstants.FAIL_TYPE);
            pr.put("data", data);
            WebsocketManager.appointSending(String.valueOf(userId), JSON.toJSONString(pr));
        });
    }


    /**
     * 查询一个设备影子，返回当前设备的所有属性
     * ****后面看情况支持多个设备，不过返回的数据结构需要改变，需要区分出设备的id
     *
     * @param ownId
     * @param deviceId
     * @return
     */
    public Map<String, String> getShadows(String ownId, String deviceId) {
        String url = equipmentConfig.getShadowsUrl();

        //请求头
        Map<String, String> header = HttpUtil.getHeader4Ruying(equipmentConfig.getAppId(), JwtUtil.getRuyingGwt(equipmentConfig.getSerectKey(), ownId));
        //请求体
        Map<String, List<String>> body = new HashMap<>();
        body.put("devices", Lists.newArrayList(deviceId));
        //发送请求到如影
        JSONObject response = HttpUtil.doPoststr(url, JSONObject.toJSONString(body), header);

        Map<String, String> result = doCheckShadow(response, ownId);
        String deviceUrl = equipmentConfig.getDeviceUrl(deviceId);
        JSONObject summerResponse = HttpUtil.doGetstr(deviceUrl, header);
        Boolean online = Optional.ofNullable(summerResponse).map(s -> s.getJSONObject("data")).map(s -> s.getJSONObject("device")).map(s -> s.getJSONObject("summary")).map(s -> s.getBoolean("online")).orElse(false);
        result.put("online", online ? "1" : "0");
        return result;
    }

    public Boolean deleteRuyingDevice(String deviceSns, String ownId) {
        String url = equipmentConfig.getDevicesUrl() + "?did=" + deviceSns;

        //请求头
        Map<String, String> header = HttpUtil.getHeader4Ruying(equipmentConfig.getAppId(), JwtUtil.getRuyingGwt(equipmentConfig.getSerectKey(), ownId));
        JSONObject jsonObject = HttpUtil.doDeletetr(url, header);
        log.info("删除如影设备,result =  {}", JSON.toJSONString(jsonObject));
        Integer status = Optional.ofNullable(jsonObject).map(s -> s.getJSONObject("data")).map(s -> s.getJSONArray("devices")).map(l -> l.getJSONObject(0)).map(s -> s.getInteger("status")).orElse(null);
        return Objects.equals(status, 0);
    }

    private RuYingResponse<EquipmentPropertiesData> doCheckResponse(JSONObject jsonObject) {
        XunkAssert.notEmpty(jsonObject, "未接收到响应信息");
        RuYingResponse<EquipmentPropertiesData> response = JSONObject.parseObject(jsonObject.toJSONString(), new TypeReference<RuYingResponse<EquipmentPropertiesData>>() {
        });
        if (!OK_MSG.equals(response.getMsg())) {
            logger.info("获取/设置属性响应信息：" + JSONObject.toJSONString(jsonObject));
            throw new RuntimeException("读取设备属性异常");
        }
        return response;
    }

    private Map<String, String> doCheckShadow(JSONObject jsonObject, String ownId) {
        XunkAssert.notEmpty(jsonObject, "未接收到响应信息");
        log.info("设备影子信息 {}", JSON.toJSONString(jsonObject));
        Long deptId = baseService.findDeptIdByOwnId(ownId);
        Map<String, String> resultMap = new HashMap<>();

        //返回结果 ok 成功
        String msg = jsonObject.getString("msg");
        if (!msg.equals("ok"
        ))
            throw new RuntimeException("设备信息返回异常");

        JSONObject data = jsonObject.getJSONObject("data");
        JSONArray devices = data.getJSONArray("devices");
        for (int i = 0; i < devices.size(); i++) {
            JSONObject device = devices.getJSONObject(i);
            if (!Objects.equals(device.getInteger("status"), 0)) {
                continue;
            }
            String did = device.getString("did");
            JSONArray shadow = device.getJSONArray("shadow");
            for (int j = 0; j < shadow.size(); j++) {
                JSONObject property = shadow.getJSONObject(j);
                if (!Objects.equals(property.getInteger("status"), 0)) {
                    continue;
                }
                String pid = did.concat(".").concat(property.getString("siid")).concat(".").concat(property.getString("piid"));
                resultMap.put(pid, property.getString("value"));

                try {
                    //存入时序数据库
                    RuyingEquiqmentInfluxEntity entity = new RuyingEquiqmentInfluxEntity(did,
                            Integer.parseInt(property.getString("siid")),
                            Integer.parseInt(property.getString("piid")),
                            property.getString("value"),
                            deptId);
                    customMapper.save(entity);
                } catch (Exception e) {
                    logger.error("设备影子入influxdb 异常", e);
                }

            }
        }
        return resultMap;
    }

    public Map<String, String> getShadowByDeviceSn(String deviceSn, Long deptId) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        Map<String, String> cacheMap = redisService.getCacheMap(key);
        if (DataUtils.isEmpty(cacheMap)) {
            try {
                cacheMap = getShadowsCache(deptId, deviceSn, key);
            } catch (Exception e) {
                log.error("getShadowByDeviceSn查询设备影子异常", e);
            }
        }
        return cacheMap;
    }

    /**
     * 缓存如影设备
     *
     * @param deptId
     * @param pid
     * @param value
     */
    public void cacheRuyingEquipmentProperty(Long deptId, String pid, String value) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        // key = 项目id:区域id:社区id:家庭id:设备id
        String did = pid.split("\\.")[0];
        String key = ancestor.concat(":").concat(did);
        Map<String, String> cacheMap = redisService.getCacheMap(key);
        if (DataUtils.isEmpty(cacheMap)) {
            getShadowsCache(deptId, did, key);
        } else {
            redisService.setCacheMapValue(key, pid, value);
        }
    }

    public void cacheRuyingEquipmentProperty(Long deptId, String did, String hKey, String value) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        // key = 项目id:区域id:社区id:家庭id:设备id
        String key = ancestor.concat(":").concat(did);
        redisService.setCacheMapValue(key, hKey, value);
//        redisService.expire(key, 2, TimeUnit.HOURS);
    }

    /**
     * 缓存设备影子
     *
     * @param deptId   为了查询ownId
     * @param deviceSn 设备号
     * @param key      缓存的key
     * @return
     */
    public Map<String, String> getShadowsCache(Long deptId, String deviceSn, String key) {
        String ownId = baseService.findOwnIdByDeptId(deptId);
        Map<String, String> shadows = getShadows(ownId, deviceSn);
        redisService.setCacheMap(key, shadows);
//        redisService.expire(key, 2, TimeUnit.HOURS);
        return shadows;
    }

    /**
     * @param deviceSn
     * @param deptId
     * @return
     */
    public Boolean deleteRuyingDevice(String deviceSn, Long deptId) {
        XunkAssert.notEmpty(deviceSn, "设备号不能为空");
        String ownId = baseService.findOwnIdByDeptId(deptId);
        // 删除设备影子
        String ruyingDeviceDefineKey = MessageFormat.format(CacheConstants.RUYING_DEVICE_DEFINE_CACHE, ownId, deviceSn);
        redisService.deleteObject(ruyingDeviceDefineKey);
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        redisService.deleteObject(key);
        redisService.deleteObject(CacheConstants.EQUIPMENT_INFO_KEY + ":" + deviceSn);
        return deleteRuyingDevice(deviceSn, ownId);
    }


    private String[] getIds(String pid) {

        //  pid 统一格式： 1@11.11.11
        String[] ids = new String[3];
        String[] iids = pid.split("\\.");
        ids[0] = iids[0];
        ids[1] = iids[1];
        ids[2] = iids[2];
        return ids;
    }

}
